package cz.cuni.amis.pogamut.episodic.visualizer;

import cz.cuni.amis.pogamut.episodic.schemas.ISchemaMessageCommand;

import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.algorithms.shortestpath.MinimumSpanningForest2;
import edu.uci.ics.jung.graph.DelegateForest;
import edu.uci.ics.jung.graph.DelegateTree;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Tree;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationModel;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractPopupGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Paint;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.apache.commons.collections15.Predicate;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.functors.ConstantTransformer;

/**
 * <code>VisualizationRenderer</code> is responsible for actual visualization
 * itself. It draws the visualization window and controls it. It does not
 * know almost anything about the nodes it is visualizing. All it does is
 * provide a group of functions for adding and manipulating with graph
 * representations of memory. All its methods are called from
 * <code>VisualizationCreator</code> class only.
 * <p>
 * The only knowlegde about the memory it has are: the index of schema bag
 * visualization graph, so that custom detail about selected counts can
 * be displayed upon request; some information about node and edges type
 * so that different edges and labels can be filtered out and so that
 * tree layout of the graphs is generated correctly without the object nodes
 * connecting several trees together.
 *
 * @author Michal Cermak (based on Jung examples by Tom Nelson)
 *
 */

public class VisualizationRenderer {
    /**
     * Constant that defines the initial top and left margin separating
     * the visualization edge from the top left node.
     * <p>
     * It is also used to define how far under the tree structures should
     * the object nodes be places.
     */
    final int MARGIN_WIDTH = 50;

    /**
     * In tree layout several nodes can get too close to each other, so that
     * its labels are overlapping and thus are difficult to read. This
     * constant limits the lenght of a vertex label in all graphs.
     */
    final int MAX_LABEL_LENGTH = 20;

    /**
     * The index of schema bag visualization graph, so that custom detail
     * about selected counts can be displayed upon request.
     */
    private int schemaGraphIndex;

    /**
     * Actual graph structures holding the visualized representation of
     * all structures in memory. Each time different graph is displayed,
     * other graphs are still part of visualizer, are just not displayed
     * for a while.
     */
    protected Map<Integer,Graph<Integer, Integer>> graphs = new HashMap<Integer,Graph<Integer,Integer>>();

    /**
     * Layouts for all the graphs in a visualizer structure. Each time
     * a different graph is visualized, the relevant layout has to be
     * used so that the visualization is user-friendly and readable.
     */
    protected Map<Integer,Layout<Integer,Integer>> layouts = new HashMap<Integer,Layout<Integer,Integer>>();

    /**
     * Names of graphs that are used to populate the graph chooser combo box
     * in a visualization window.
     */
    protected Map<Integer,String> graph_names = new HashMap<Integer,String>();

    protected Integer lastSchemaEpisodeNode = 0;
    protected Map<Integer,Integer> lastNonObjectNode = new HashMap<Integer,Integer>();

    /**
     * This structure holds an additional info about all vertices in all
     * the graphs. Among this information can be labels, vertex color.
     * Correct information can be accessed from a map indexed by unique
     * vertex ID.
     */
    private AdditionalInfo info = new AdditionalInfo();

    /**
     * Normally visualizations can be refreshed each time an agent performs
     * some action. Because maintaining tree layout does not keep the order
     * of individual trees, it may be needed to pause the constant redrawal
     * of visualized graphs. When this variable is set to false, no graphs
     * are refreshed and the visualizations can be studied more thoroughly.
     */
    boolean refresh = true;

    /**
     * This structure holds an additional info about all vertices in all
     * the graphs. Among this information can be labels, vertex color.
     * Correct information can be accessed from a map indexed by unique
     * vertex ID.
     */
    public class AdditionalInfo {
        /**
         * Labels of vertices stored in a map indexed by vertex ID.
         */
        Map<Integer, String> vertexLabels = new HashMap<Integer, String>();
        /**
         * Tooltips displayed when mouse is hovered over a vertex
         * stored in a map indexed by vertex ID.
         */
        Map<Integer, String> vertexTooltips = new HashMap<Integer, String>();
        /**
         * Detailed information about vertices displayed on right-click
         * on a vertex. Stored in a map indexed by vertex ID.
         */
        Map<Integer, String> vertexDetail = new HashMap<Integer, String>();
        /**
         * Tooltips displayed when mouse is hovered over an edge
         * stored in a map indexed by vertex ID.
         */
        Map<Integer, String> edgeTooltips = new HashMap<Integer, String>();
        /**
         * Types of all the vertices in all displayable graphs. Needed to
         * correctly filter labels, distinquish between object nodes and rest
         * when creating tree layout, etc. Stored in a map indexed by vertex ID.
         */
        Map<Integer, VertexType> vertexTypes = new HashMap<Integer, VertexType>();
        /**
         * Colors of edges store in a map indexed by edge ID.
         */
        Map<Integer, Paint> edgeColors = new HashMap<Integer, Paint>();
        /**
         * Types of all the edges in all displayable graphs. Needed to
         * correctly filter edges, distinquish between object nodes and rest
         * when creating tree layout, etc. Stored in a map indexed by vertex ID.
         */
        Map<Integer, EdgeType> edgeType = new HashMap<Integer, EdgeType>();
        /**
         * Colors of vertices store in a map indexed by edge ID.
         */
        Map<Integer, Color> vertexColors = new HashMap<Integer, Color>();
        /**
         * Equivalent of node score. Nodes with high score will be more
         * saturated. Nodes where the score is not computed will not be
         * in this map and their color will not turn gray.
         */
        Map<Integer, Double> vertexSaturation = new HashMap<Integer, Double>();
        /**
         * Map that decides whether labels of particular vertex type should
         * be displayed. Indexed by <code>VertexType</code>s.
         */
        Map<VertexType, Boolean> showLabels = new HashMap<VertexType, Boolean>();

        /**
         * This method can be used to remove an edge an all information about
         * it from <code>AdditionalInfo</code> structure.
         *
         * @param e ID of an edge that is to be removed.
         */
        void removeEdge(Integer e) {
            edgeColors.remove(e);
            edgeTooltips.remove(e);
            edgeType.remove(e);
        }

        /**
         * This method can be used to remove a vertex an all information about
         * it from <code>AdditionalInfo</code> structure.
         *
         * @param e ID of a vertex that is to be removed.
         */
        void removeVertex(Integer v) {
            vertexColors.remove(v);
            vertexDetail.remove(v);
            vertexLabels.remove(v);
            vertexTooltips.remove(v);
            vertexTypes.remove(v);
            vertexSaturation.remove(v);
        }
    }

    /**
     * Collection of all vertices representing object nodes indexed by vertex
     * ID. Needed so that object vertices can be added to graph visualization
     * after creating the tree layout.
     */
    Map<Integer, ObjectVertex> objectVertices = new HashMap<Integer, ObjectVertex>();

    /**
     * Class holding information about vertex representing an object node.
     * These vertices need to be removed from graphs before creation of
     * a tree layout and then returned back. This class hold all necessary
     * information needed to return the vertex back to its graph(s).
     * Additional information about vertex such as color, label, etc. were
     * not deleted.
     */
    public class ObjectVertex {
        /**
         * ID of vertex representing object node.
         */
        public int id;
        /**
         * Graphs the vertex belongs to. Because one object node can belong to
         * several chronobags, its vertex representation has same ID for all
         * chronobag visualization and thus it has to be added to all these
         * graphs under the same one ID.
         */
        public HashSet<Integer> graphs = new HashSet<Integer>();
        /**
         * ID of slot vertices this vertex is connected to.
         * Indexed by ID of an edge.
         */
        public HashMap<Integer, Integer> slots = new HashMap<Integer, Integer>();
        /**
         * Instantiate the class by providing the ID of a new vertex.
         *
         * @param _id   Unique ID of the vertex representing the object.
         */
        public ObjectVertex(int _id) {
            id = _id;
        }
    }

    /**
     * Collection of all nodes that are not part of the <strong>tree</strong>
     * structures. These can be for example various sequence edges. They
     * need to be removed before creating the tree layout of a graph, so that
     * correct spanning trees will be generated. They will be returned later
     * and thus have to be remembered in this map.
     */
    Map<Integer, HideEdge> noSpanningEdges = new HashMap<Integer, HideEdge>();

    /**
     * Class holding information about an edge that is not part of
     * the <strong>tree</strong> structures. These can be for example various
     * sequence edges. They need to be removed before creating the tree
     * layout of a graph, so that correct spanning trees will be generated.
     * They will be returned later and thus all its basic properties have
     * to be remembered in a special class.
     */
    public class HideEdge {
        /**
         * ID of an edge.
         */
        int id;
        /**
         * Index of a graph this edge belongs to.
         */
        int graph;
        /**
         * Vertex on one end of this edge.
         */
        Integer v1;
        /**
         * Vertex on the other end of this edge.
         */
        Integer v2;

        /**
         * Instantiate the class by providing all necessary information about
         * the edge so that it can be automatically returned to correct graph.
         *
         * @param _id   ID of an edge.
         * @param _graph    Index of a graph this edge belongs to.
         * @param _v1   Vertex on one end of this edge.
         * @param _v2   Vertex on the other end of this edge.
         */
        HideEdge(int _id, int _graph, int _v1, int _v2) {
            id = _id;
            graph = _graph;
            v1 = _v1;
            v2 = _v2;
        }
    }

    /**
     * Index of a graph that is currently displayed in the visualizer window.
     */
    protected int graph_index;

    /**
     * Listener attached to the combobox choosing the graph that should be
     * displayed in a visualization window.
     */
    public class GraphChooser implements ActionListener
    {
        /**
         * Reference to the parent <code>VisualizationRenderer</code> class.
         */
        VisualizationRenderer viz;

        /**
         * Instantiate the class by providing a reference to the parent
         * <code>VisualizationRenderer</code> class.
         *
         * @param _viz  Reference to the parent <code>VisualizationRenderer</code> class.
         */
        public GraphChooser(VisualizationRenderer _viz)
        {
            viz = _viz;
        }

        /**
         * This method is called each time different graph is chosen to be
         * displayed in a visualization window. It is responsible for
         * displaying the correct graph.
         * @param e Event generated by a combobox when new graph was chosen
         * to be displayed.
         */
        public void actionPerformed(ActionEvent e)
        {
            JComboBox cb = (JComboBox)e.getSource();
            graph_index = cb.getSelectedIndex();
            viz.drawGraph(graph_index);
            if (lastNonObjectNode.containsKey(graph_index)) {
                positionObjectNodes(lastNonObjectNode.get(graph_index) + MARGIN_WIDTH);
            }
        }
    }

    /**
     * Reference to the class providing actual counts for different set
     * of nodes in schema bag. Visualization renderer does not remember
     * the detailed information for set of nodes, thus the message
     * is retrieved directly from the schema bag via
     * the <code>SchemaMessageCommand</code> class.
     */
    ISchemaMessageCommand schemaMessageCommand = null;

    /**
     * Text area that hold the detailed information about vertices displayed
     * upon right click on a vertex.
     */
    JTextArea textArea;
    /**
     * A component to provide a scrollable view of <code>textArea</code>
     */
    JScrollPane scrollPane;
    /**
     * Panel with vertex detail information that should be displayed upon
     * right click.
     */
    JDialog infoDialog = new JDialog();

    /**
     * Listener object that is responsible for making popup window visible
     * upon right-click. It also finds the correct text to be displayed
     * and fills the popuped textarea.
     */
    protected class PopupGraphMousePlugin extends AbstractPopupGraphMousePlugin
    	implements MouseListener {

        public PopupGraphMousePlugin() {
            this(MouseEvent.BUTTON3_MASK);
        }
        public PopupGraphMousePlugin(int modifiers) {
            super(modifiers);
        }

        /**
         * If this event is over a Vertex, pop up a new window with detailed
         * information about a vertex. If the vertex is part of a schema bag
         * display count for selected set of vertices.
         */
        @SuppressWarnings("unchecked")
        protected void handlePopup(MouseEvent e) {
            final VisualizationViewer<Integer,Number> vv =
                (VisualizationViewer<Integer,Number>)e.getSource();
            Point2D p = e.getPoint();

            if (graph_index == schemaGraphIndex) {
                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
                textArea.setText(schemaMessageCommand.getSchemaMessage(picked));
                infoDialog.setVisible(true);
                return;
            }

            GraphElementAccessor<Integer,Number> pickSupport = vv.getPickSupport();
            if(pickSupport != null) {
                final Integer v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY());
                if(v != null) {
                    if (info.vertexDetail.containsKey(v)) {
                        textArea.setText(info.vertexDetail.get(v));
                        infoDialog.setVisible(true);
                    }
                } else {
                    final Number edge = pickSupport.getEdge(vv.getGraphLayout(), p.getX(), p.getY());
                    if(edge != null) {

                    }
                }
            }
        }
    }

    /**
     * Class to determine whether each edge should be displayed based
     * on currect options.
     */
    private EdgeDisplayPredicate<Integer, Integer> showEdge;

    /**
     * Class to determine whether each edge should be displayed based
     * on currect options.
     */
    private final class EdgeDisplayPredicate<V,E>
    	implements Predicate<Context<Graph<V,E>,E>>
    {
        /**
         * True if approriate checkbox is checked and edges connecting
         * nodes with their child nodes should be displayed.
         */
        protected boolean showSubNodes;
        /**
         * True if approriate checkbox is checked and sequence edges
         * should be displayed.
         */
        protected boolean showSequence;
        /**
         * True if approriate checkbox is checked and edges connecting
         * objects with slots should be displayed.
         */
        protected boolean showObject;

        /**
         * Instantiate the predicate by telling edges of what types should
         * be displayed.
         *
         * @param showSub   True if edges between nodes and direct children
         * should be displayed.
         * @param showSeq   True if sequence edges should be displayed.
         * @param showObj   True if edges connecting object nodes to slots
         * should be displayed.
         */
        public EdgeDisplayPredicate(boolean showSub, boolean showSeq, boolean showObj)
        {
            this.showSubNodes = showSub;
            this.showSequence = showSeq;
            this.showObject = showObj;
        }

        /**
         * Change the value of <code>showSubNodes</code> variable. Call if
         * value of appropriate checkbox has changed.
         *
         * @param b True if edges between nodes and direct children
         * should be displayed.
         */
        public void showSubNodes(boolean b)
        {
            showSubNodes = b;
        }

        /**
         * Change the value of <code>showSequence</code> variable. Call if
         * value of appropriate checkbox has changed.
         *
         * @param b True if sequence edges should be displayed.
         */
        public void showSequence(boolean b)
        {
            showSequence = b;
        }

        /**
         * Change the value of <code>showObject</code> variable. Call if
         * value of appropriate checkbox has changed.
         *
         * @param b True if edges connecting object nodes to slots
         * should be displayed.
         */
        public void showObject(boolean b)
        {
            showObject = b;
        }

        /**
         * Determines whether given edge should be displayed or not.
         *
         * @param context   Object containing ID of a node that is being drawn.
         * @return  Returns true of the edge should be displayed, false otherwise.
         */
        public boolean evaluate(Context<Graph<V,E>,E> context)
        {
            E e = context.element;
            Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
            Integer id = (Integer) e;
            if (picked.contains(graphs.get(graph_index).getDest(id)) ||
                picked.contains(graphs.get(graph_index).getSource(id))) {
                return true;
            }
            if (info.edgeType.get(id) == EdgeType.SUBNODE && showSubNodes) {
                return true;
            }
            if (info.edgeType.get(id) == EdgeType.SEQUENCE && showSequence) {
                return true;
            }
            if (info.edgeType.get(id) == EdgeType.OBJECT && showObject) {
                return true;
            }
            return false;
        }
    }

    /**
     * the visual component and renderer for the graph
     */
    VisualizationViewer<Integer,Integer> vv;
    VisualizationModel<Integer,Integer> vm;

    /**
     * Combobox used to choose visualization of which memory structure should
     * be displayed in a visualizer window.
     */
    JComboBox graph_chooser;

    /**
     * Construtor of a renderer. All window objects are created here.
     */
    public VisualizationRenderer() {
        //empty graph is created first so that there is anything to display
        Graph<Integer,Integer> graph = new DirectedSparseGraph<Integer,Integer>();
        Layout<Integer,Integer> layout = new StaticLayout<Integer,Integer>(graph);

        vm = new DefaultVisualizationModel<Integer,Integer>(layout);

        vv = new VisualizationViewer<Integer,Integer>(vm);

        //sets the predicate telling which edges should be displayed and which should be hidden
        showEdge = new EdgeDisplayPredicate<Integer, Integer>(true, true, true);
        vv.getRenderContext().setEdgeIncludePredicate(showEdge);

        //set a transformer to display correct vertex labels
        vv.getRenderContext().setVertexLabelTransformer(new Transformer<Integer,String>(){
            public String transform(Integer v) {
                if (info.showLabels.get(VertexType.DEFAULT_VERTEX))
                    if (!info.vertexTypes.containsKey(v) || info.showLabels.get(info.vertexTypes.get(v)))
                        return info.vertexLabels.get(v);
                return "";
            }});

        //set a transformer to use correct color when drawing each vertex
        vv.getRenderContext().setVertexFillPaintTransformer(new Transformer<Integer,Paint>() {
            public Paint transform(Integer v) {
                if(vv.getPickedVertexState().isPicked(v)) {
                    return Color.YELLOW;
                }
                if (info.vertexColors.containsKey(v)) {
                    if (info.vertexSaturation.containsKey(v)) {
                        double saturation = info.vertexSaturation.get(v);
                        Color c = info.vertexColors.get(v);
                        int blue = c.getBlue();
                        int red = c.getRed();
                        int green = c.getGreen();
                        blue = (int) Math.round((255 - blue) * (1 - saturation)) + blue;
                        red = (int) Math.round((255 - red) * (1 - saturation)) + red;
                        green = (int) Math.round((255 - green) * (1 - saturation)) + green;
                        return new Color(red, green, blue);
                    }
                    return info.vertexColors.get(v);
                }
                return Color.RED;
            }
        });

        //set a transformer to display correct edge tooltips
        vv.setEdgeToolTipTransformer(new Transformer<Integer, String>() {
            public String transform(Integer v) {
                if (info.edgeTooltips.containsKey(v)) {
                    return info.edgeTooltips.get(v);
                }
                return "";
            }
        });
        //set a transformer to display correct vertex tooltips
        vv.setVertexToolTipTransformer(new Transformer<Integer,String>() {
            public String transform(Integer v) {
                if (info.vertexTooltips.containsKey(v)) {
                    return info.vertexTooltips.get(v);
                }
                return info.vertexLabels.get(v);
            }
        });

        //set a transformer to use correct color when drawing edge lines
        vv.getRenderContext().setEdgeDrawPaintTransformer(new Transformer<Integer,Paint>() {
            public Paint transform(Integer e) {
                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
                Integer id = (Integer) e;
                if (picked.contains(graphs.get(graph_index).getDest(id)) ||
                    picked.contains(graphs.get(graph_index).getSource(id))) {
                    return Color.BLACK;
                }
                if (info.edgeColors.containsKey(e)) {
                    return info.edgeColors.get(e);
                }
                return Color.LIGHT_GRAY;
            }
        });
        //set a transformer to use correct color when drawing edge arrows
        vv.getRenderContext().setArrowDrawPaintTransformer(new Transformer<Integer,Paint>() {
            public Paint transform(Integer e) {
                if (info.edgeColors.containsKey(e)) {
                    return info.edgeColors.get(e);
                }
                return Color.BLACK;
            }
        });
        vv.getRenderContext().setArrowFillPaintTransformer(new Transformer<Integer,Paint>() {
            public Paint transform(Integer e) {
                if (info.edgeColors.containsKey(e)) {
                    return info.edgeColors.get(e);
                }
                return Color.BLACK;
            }
        });

        // create a frame to hold the graph
        final JFrame frame = new JFrame();
        frame.setTitle("Chronobag Viewer");
        Container content = frame.getContentPane();
        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
        content.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //object responsible for displaying popups with detailed information
        final DefaultModalGraphMouse<Integer,Integer> gm = new DefaultModalGraphMouse<Integer,Integer>();
        vv.setGraphMouse(gm);
        vv.addKeyListener(gm.getModeKeyListener());
        gm.setMode(ModalGraphMouse.Mode.TRANSFORMING);
        gm.add(new PopupGraphMousePlugin());

        //initializes the popup window
        textArea = new JTextArea(6,30);
        scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.setEditable(false);

        infoDialog.getContentPane().add(scrollPane);
        infoDialog.pack();
        infoDialog.setAlwaysOnTop(true);
        infoDialog.setSize(new Dimension(300,300));

        vv.setName("");

        //time information - object that adds graph label with currect time
        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
            int x;
            int y;
            Font font;
            FontMetrics metrics;
            int swidth;
            int sheight;
            String str;

            public void paint(Graphics g) {
                str = vv.getName();
                Dimension d = vv.getSize();
                if(font == null) {
                    font = new Font(g.getFont().getName(), Font.BOLD, 20);
                    metrics = g.getFontMetrics(font);
                    swidth = metrics.stringWidth(str);
                    sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
                    //x = (d.width-swidth) - 50;
                    x = 50;
                    y = (int)(d.height-sheight*1.5);
                }
                g.setFont(font);
                Color oldColor = g.getColor();
                g.setColor(Color.DARK_GRAY);
                g.drawString(str, x, y);
                g.setColor(oldColor);
            }
            public boolean useTransform() {
                return false;
            }
        });

        //graph selector
        final Box graph_panel = Box.createVerticalBox();
        graph_panel.setBorder(BorderFactory.createTitledBorder("Structure"));

        graph_chooser = new JComboBox(graph_names.values().toArray());
        graph_chooser.addActionListener(new GraphChooser(this));

        graph_panel.add(graph_chooser);

        //refresh checkbox
        JCheckBox refreshCheckBox = new JCheckBox("Refresh");
        refreshCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                refresh = e.getStateChange() == ItemEvent.SELECTED;
                if (graph_index == schemaGraphIndex && graphs.containsKey(schemaGraphIndex) &&
                        graphs.get(schemaGraphIndex).getVertexCount() > 0) {
                    returnNoSpanningEdges(schemaGraphIndex, lastSchemaEpisodeNode);
                }
                if (lastNonObjectNode.containsKey(graph_index)) {
                    positionObjectNodes(lastNonObjectNode.get(graph_index) + MARGIN_WIDTH);
                }
                vv.repaint();
            }
        });
        refreshCheckBox.setSelected(true);
        graph_panel.add(refreshCheckBox);

        //label controls
        final Box labels_panel = Box.createVerticalBox();
        labels_panel.setBorder(BorderFactory.createTitledBorder("Labels"));

        JCheckBox allLabelsCheckBox = new JCheckBox("All Labels");
        allLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.DEFAULT_VERTEX, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.DEFAULT_VERTEX, Boolean.TRUE);
        allLabelsCheckBox.setSelected(true);
        labels_panel.add(allLabelsCheckBox);

        JCheckBox actionLabelsCheckBox = new JCheckBox("Actions");
        actionLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.ACTION, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.ACTION, Boolean.TRUE);
        actionLabelsCheckBox.setSelected(true);
        labels_panel.add(actionLabelsCheckBox);

        JCheckBox intentionLabelsCheckBox = new JCheckBox("Intentions");
        intentionLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.INTENTION, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.INTENTION, Boolean.TRUE);
        intentionLabelsCheckBox.setSelected(true);
        labels_panel.add(intentionLabelsCheckBox);

        JCheckBox atomicLabelsCheckBox = new JCheckBox("Atomic actions");
        atomicLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.ATOMIC_ACTION, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.ATOMIC_ACTION, Boolean.TRUE);
        atomicLabelsCheckBox.setSelected(true);
        labels_panel.add(atomicLabelsCheckBox);

        JCheckBox affordancesLabelsCheckBox = new JCheckBox("Affordances");
        affordancesLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.AFFORDANCE, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.AFFORDANCE, Boolean.TRUE);
        affordancesLabelsCheckBox.setSelected(true);
        labels_panel.add(affordancesLabelsCheckBox);

        JCheckBox objectsLabelsCheckBox = new JCheckBox("Object nodes");
        objectsLabelsCheckBox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                info.showLabels.put(VertexType.OBJECT, (Boolean) (e.getStateChange() == ItemEvent.SELECTED));
                vv.repaint();
            }
        });
        info.showLabels.put(VertexType.OBJECT, Boolean.TRUE);
        objectsLabelsCheckBox.setSelected(true);
        labels_panel.add(objectsLabelsCheckBox);

        //edge selector
        Box showEdgePanel = Box.createVerticalBox();
        showEdgePanel.setBorder(BorderFactory.createTitledBorder("Edges"));

        JCheckBox subNodesCheckbox = new JCheckBox("Subnode edges");
        subNodesCheckbox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                showEdge.showSubNodes(e.getStateChange() == ItemEvent.SELECTED);
                vv.repaint();
            }
        });
        subNodesCheckbox.setSelected(true);
        showEdgePanel.add(subNodesCheckbox);

        JCheckBox sequenceCheckbox = new JCheckBox("Sequence edges");
        sequenceCheckbox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                showEdge.showSequence(e.getStateChange() == ItemEvent.SELECTED);
                vv.repaint();
            }
        });
        sequenceCheckbox.setSelected(true);
        showEdgePanel.add(sequenceCheckbox);

        JCheckBox objectEdgeCheckbox = new JCheckBox("Object edges");
        objectEdgeCheckbox.addItemListener(new ItemListener(){
            public void itemStateChanged(ItemEvent e) {
                showEdge.showObject(e.getStateChange() == ItemEvent.SELECTED);
                vv.repaint();
            }
        });
        objectEdgeCheckbox.setSelected(true);
        showEdgePanel.add(objectEdgeCheckbox);

        //add all the controls into one panel
        JPanel modePanel = new JPanel();
        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
        modePanel.add(gm.getModeComboBox());

        JPanel middlePanel = new JPanel(new GridLayout(0,1));
        middlePanel.add(labels_panel, BorderLayout.CENTER);
        middlePanel.add(showEdgePanel, BorderLayout.CENTER);

        JPanel controls = new JPanel();
        controls.setLayout(new BorderLayout());
        controls.add(graph_panel, BorderLayout.NORTH);
        controls.add(middlePanel, BorderLayout.CENTER);
        controls.add(modePanel, BorderLayout.SOUTH);

        content.add(controls, BorderLayout.EAST);

        frame.pack();
        frame.setVisible(true);
    }

    /*private boolean checkAllLabels() {
        return (info.showActions && info.showAffordances && info.showAtomicActions && info.showIntentions);
    }*/

    /**
     * Sets the <code>schemaGraph</code> index and <code>SchemaMessageCommand</code>
     * class so that count info can be displayed correctly.
     *
     * @param command   Reference to a <code>SchemaMessageCommand</code> class
     * providing text to be displayed for selected nodes when popup window
     * with actual schema count is invoked.
     * @param schemaGraph   Index of a graph visualizing schema bag, so
     * the renderer will know when to use schemaMessageCommand to retrieve
     * the info text and when to use inner <code>AdditionalInfo</code> class.
     */
    public void setSchemaMessageCommand (ISchemaMessageCommand command, int schemaGraph) {
        schemaGraphIndex = schemaGraph;
        schemaMessageCommand = command;
    }

    /**
     * Refreshes the time information displayed on visualized graph.
     *
     * @param time   String with time information to be displayed in a visualizer window.
     */
    public void updateTime(String time) {
        vv.setName(time);
        vv.repaint();
    }

    /**
     * Updates the positions of object nodes on the visible display so they
     * are always displayed on the screen as the user scrolls across the graph.
     *
     * @param time   String with time information to be displayed in a visualizer window.
     */
    public void positionObjectNodes(int y) {
        y += MARGIN_WIDTH;
        Double x = -vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTranslateX();

        for (ObjectVertex v : objectVertices.values()) {
            boolean contain = v.graphs.contains(graph_index);
            if (contain) {
                x += MARGIN_WIDTH;
                layouts.get(graph_index).setLocation(v.id, new java.awt.geom.Point2D.Double(x,y));

            }
        }
    }

    /**
     * Set the new graph that should be drawn in a visualizer window and redraws it.
     *
     * @param index Index of a graph that should be displayed.
     */
    private void drawGraph(int index) {
        vm.setGraphLayout(layouts.get(index));
    }

    /**
     * Adds a new vertex to the specified graph.
     *
     * @param index Index of a graph the vertex should be added to.
     * @param id    Unique ID of a new vertex.
     * @param label String that will be used as vertex label.
     */
    public void addVertex(int index, int id, String label) {
        graphs.get(index).addVertex(new Integer(id));
        info.vertexLabels.put(id, label);
    }

    /**
     * Adds a new vertex to the specified graph while setting some other info.
     *
     * @param index Index of a graph the vertex should be added to.
     * @param id    Unique ID of a new vertex.
     * @param label String that will be used as vertex label.
     * @param color Color of a new vertex.
     * @param type  <code>VertexType</code> of a new vertex.
     */
    public void addVertex(int index, int id, String label, Color c, VertexType type) {
        addVertex(index, id, 0, 0, label, c, type);
    }

    /**
     * Adds a new vertex to the specified graph while setting some other info
     * and positioning the vertex on a given position.
     * <p>
     * The position can change if <code>createTreeLayout</code> method is
     * invoked for the given graph.
     *
     * @param index Index of a graph the vertex should be added to.
     * @param id    Unique ID of a new vertex.
     * @param x Horizontal coordinate of a new vertex.
     * @param y Vertical coordinate of a new vertex.
     * @param label String that will be used as vertex label.
     * @param color Color of a new vertex.
     */
    public void addVertex(int index, int id, double x, double y, String label, Color c) {
        addVertex(index, id, x, y, label, c, VertexType.DEFAULT_VERTEX);
    }

    /**
     * Adds a new vertex to the specified graph while setting some other info
     * and positioning the vertex on a given position.
     * <p>
     * The position can change if <code>createTreeLayout</code> method is
     * invoked for the given graph.
     *
     * @param index Index of a graph the vertex should be added to.
     * @param id    Unique ID of a new vertex.
     * @param x Horizontal coordinate of a new vertex.
     * @param y Vertical coordinate of a new vertex.
     * @param label String that will be used as vertex label.
     * @param color Color of a new vertex.
     * @param type  <code>VertexType</code> of a new vertex.
     */
    public void addVertex(int index, int id, double x, double y, String label, Color c, VertexType type) {
        if (label.length() > MAX_LABEL_LENGTH) label = label.substring(0, MAX_LABEL_LENGTH);
        graphs.get(index).addVertex(new Integer(id));
        info.vertexLabels.put(id, label);
        info.vertexTypes.put(id, type);
        info.vertexColors.put(id, c);
        if (type == VertexType.OBJECT) {
            ObjectVertex objectVertex = objectVertices.get(id);
            if (objectVertex == null) {
                objectVertex = new ObjectVertex(id);
            }
            objectVertex.graphs.add(index);
            objectVertices.put(id, objectVertex);
        }
        layouts.get(index).setLocation(id, new java.awt.geom.Point2D.Double(x + MARGIN_WIDTH,y + MARGIN_WIDTH));
    }

    /**
     * Position an existing vertex to a specified location.
     *
     * @param graph Index of a graph the vertex belongs to.
     * @param id    ID of a vertex that is supposed to be moved.
     * @param x New X coordination.
     * @param y New Y coordination.
     */
    public void setVertexLocation(int graph, int id, double x, double y) {
        layouts.get(graph).setLocation(id, new java.awt.geom.Point2D.Double(x + MARGIN_WIDTH,y + MARGIN_WIDTH));
    }

    /**
     * Adds a new edge into a specified graph. Edge will be connecting specified
     * vertices. No edge is added if there already exists an edge between
     * specified the vertices.
     *
     * @param index Index of a graph the edge should be added to.
     * @param id    Unique ID of a new edge.
     * @param v1    ID of a vertex on one end of this edge.
     * @param v2    ID of a vertex on the other end of this edge.
     * @param label Label of a new edge.
     * @param c Color of the new edge.
     * @return  Returns true if new edge was added to the graph. Returns false
     * if an edge already existed between the specified vertices.
     */
    public boolean addEdge(int index, int id, int v1, int v2, String label, Color c) {
        return addEdge(index, id, EdgeType.SUBNODE, v1, v2, label, c);
    }

    /**
     * Adds a new edge into a specified graph. Edge will be connecting specified
     * vertices. No edge is added if there already exists an edge between
     * specified the vertices.
     *
     * @param index Index of a graph the edge should be added to.
     * @param id    Unique ID of a new edge.
     * @param type  <code>EdgeType</code> of a new edge.
     * @param v1    ID of a vertex on one end of this edge.
     * @param v2    ID of a vertex on the other end of this edge.
     * @param label Label of a new edge.
     * @param c Color of the new edge.
     * @return  Returns true if new edge was added to the graph. Returns false
     * if an edge already existed between the specified vertices.
     */
    public boolean addEdge(int index, int id, EdgeType type, int v1, int v2, String label, Color c) {
        boolean success = graphs.get(index).addEdge(id, v1, v2);
        if (!success) return false;

        info.edgeColors.put(id, c);
        info.edgeType.put(id, type);
        if (!type.spanning) noSpanningEdges.put(id, new HideEdge(id, index, v1, v2));
        if (type == EdgeType.OBJECT) {
            ObjectVertex objectVertex = null;
            if (objectVertices.containsKey(v1)) objectVertex = objectVertices.get(v1);
            if (objectVertices.containsKey(v2)) objectVertex = objectVertices.get(v2);
            if (objectVertex == null) return true;
            objectVertex.graphs.add(index);
            if (info.vertexTypes.get(v1) == VertexType.AFFORDANCE) {
                objectVertex.slots.put(id, v1);
            }
            if (info.vertexTypes.get(v2) == VertexType.AFFORDANCE) {
                objectVertex.slots.put(id, v2);
            }
        }
        return true;
    }

    /**
     * Deletes a given edge from specified graph.
     *
     * @param index Index of a graph containing the edge that should be removed.
     * @param id    ID of an edge that should be removed.
     */
    public void removeEdge(int index, int id) {
        info.edgeColors.remove(id);
        info.edgeType.remove(id);

        noSpanningEdges.remove(id);

        graphs.get(index).removeEdge(id);
    }

    /**
     * Sets a text that should be displayed when additional info is displayed
     * after right clicking on the specified vertex.
     * @param id    ID of a vertex with the new detailed info.
     * @param tip   Text containing detialed info of a vertex.
     */
    public void setVertexDetail(int id, String tip) {
        info.vertexDetail.put(id, tip);
    }

    /**
     * Set the saturation of a vertex based on its score. This way it should
     * be easy to differentiate between vertices with high and low score values.
     * @param id    ID of a vertex with newly specified saturation.
     * @param saturation    Saturation value. One means is normal color.
     * Zero means gray color.
     */
    public void setVertexSaturation(int id, double saturation) {
        assert saturation <= 1;
        assert saturation >= 0;
        info.vertexSaturation.put(id, saturation);
    }

    /**
     * Sets a text that should be displayed when mouse is hovering over a vertex.
     * @param id    ID of a vertex with the new tooltip.
     * @param tip   Text containing a tooltip for a vertex.
     */
    public void setVertexTooltip(int id, String tip) {
        info.vertexTooltips.put(id, tip);
    }

    /**
     * Sets a text that should be displayed when mouse is hovering over an edge.
     * @param id    ID of a edge with the new tooltip.
     * @param tip   Text containing a tooltip for an edge.
     */
    public void setEdgeTooltip(int id, String tip) {
        info.edgeTooltips.put(id, tip);
    }

    /**
     * Adds a new graph to the list of graphs that can be displayed by
     * the visualizer and adds a new item to the combobox. This graph
     * will be empty but it will be possible to add new vertices and
     * edges to it from now on.
     *
     * @param index Index of a new graph. If the index is not unique the
     * new graph will not be added.
     * @param label Name of the new graph to be displayed in a combobox.
     * @param dim   Dimension of a view window.
     * @return  Returns true if new graph was successfully added, false otherwise.
     */
    public boolean addGraph(int index, String label, Dimension dim) {
        if (dim == null) {
            dim = new Dimension(1000,500);
        }
        if (graph_names.containsValue(label) || graphs.containsKey(index)) return false;
        graphs.put(index, new DirectedSparseGraph<Integer,Integer>());

        graph_names.put(index, label);
        layouts.put(index, new StaticLayout<Integer,Integer>(graphs.get(index)));
        dim.height += 2 * MARGIN_WIDTH;
        dim.width += 2 * MARGIN_WIDTH;
        layouts.get(index).setSize(dim);
        graph_chooser.addItem(label);

        return true;
    }

    /**
     * Removes all edges and vertices from a specified graph. Should be called
     * before deleting any graph.
     *
     * @param index Index of a graph that should be emptied.
     */
    private void removeVertices(int index) {
        Graph<Integer, Integer> g = graphs.get(index);
        Collection<Integer> col = new HashSet<Integer>();
        col.addAll(g.getEdges());
        for (Integer e : col) {
            noSpanningEdges.remove(e);
            info.removeEdge(e);
            g.removeEdge(e);
        }
        col.clear();
        col.addAll(g.getVertices());
        for (Integer v : col) {
            if (objectVertices.containsKey(v)) {
                objectVertices.get(v).graphs.remove(index);
                if (objectVertices.get(v).graphs.isEmpty()) {
                    objectVertices.remove(v);
                }
            }
            info.removeVertex(v);
            g.removeVertex(v);
        }
    }

    /**
     * Deletes a specified graph from list of possibly visualized graphs.
     * This method is called during memory reorganization phase, so that
     * chronobags can be heavily altered are displayed from scratch.
     * @param index Index of a graph that is to be removed.
     * @return  Returns true if graph was deleted. Returns false if graph with
     * no such index existed.
     */
    public boolean removeGraph(int index) {
         if (!graphs.containsKey(index)) return false;
         clearGraph(index);
         graphs.remove(index);
         graph_chooser.removeItem(graph_names.get(index));
         graph_names.remove(index);
         layouts.remove(index);
         return true;
    }

    public boolean clearGraph(int index) {
         if (!graphs.containsKey(index)) return false;
         for (Iterator<Map.Entry<Integer, HideEdge>> i = noSpanningEdges.entrySet().iterator(); i.hasNext(); ) {
             Map.Entry<Integer, HideEdge> entry = i.next();
             if (entry.getValue().graph == index) {
                 i.remove();
             }
         }

         removeVertices(index);
         return true;
    }

    /**
     * Removes object vertices and sequence edges from the specified graph.
     * This method should be called before the tree layout is created for
     * a graph, so the minimum spanning trees a correct. Removed edges and
     * vertices will be returned back later.
     *
     * @param index Index of a graph the vertices and edges should be removed from.
     */
    private void removeNoSpanningEdges(int index) {
        for (HideEdge e : noSpanningEdges.values()) {
            if (e.graph == index) {
                graphs.get(index).removeEdge(e.id);
            }
        }
        for (ObjectVertex v : objectVertices.values()) {
            boolean foo = v.graphs.contains(index);
            if (foo) {
            //Funny!! This kept execting the inner statement: if(false) { executed_statatement; }
            //if (v.graphs.contains(index)) {
                graphs.get(index).removeVertex(v.id);
            }
        }
    }

    /**
     * Returns object vertices and sequence edges to the specified graph.
     * This method should be called after the tree layout is created for
     * a graph because these edges and vertices had to be removed.
     *
     * @param index Index of a graph the vertices and edges should be returned to.
     */
    private void returnNoSpanningEdges(int index, int depth) {
        for (HideEdge e : noSpanningEdges.values()) {
            if (e.graph == index) {
                graphs.get(index).addEdge(e.id, e.v1, e.v2);
            }
        }
        Double x = -vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTranslateX();
        for (ObjectVertex v : objectVertices.values()) {
            if (v.graphs.contains(index)) {
                graphs.get(index).addVertex(v.id);

                layouts.get(index).setLocation(v.id, new java.awt.geom.Point2D.Double(x + MARGIN_WIDTH,depth + MARGIN_WIDTH));
                x += MARGIN_WIDTH;
            }
        }
    }

    /**
     * Creates tree layout of a given graph using algorithm for finding
     * a minimum spanning tree.
     * <p>
     * For the algorithm to work, graph has to be a forest, so sequence nodes
     * and object vertices are removed from graph first and returned back
     * after the new layout is created.
     *
     * @param index Index of a graph that should have the tree layout.
     */
    public void createTreeLayout(int index) {
        removeNoSpanningEdges(index);

        Graph<Integer,Integer> graph = graphs.get(index);
        if (graph == null) return;

        Forest<Integer,Integer> tree;

        MinimumSpanningForest2<Integer,Integer> prim =
        	new MinimumSpanningForest2<Integer,Integer>(graph,
        		new DelegateForest<Integer,Integer>(), DelegateTree.<Integer,Integer>getFactory(),
        		new ConstantTransformer(1.0));

        tree = prim.getForest();

        int depth = 0;
        Collection<Tree<Integer, Integer>> trees =  tree.getTrees();
        for (Tree<Integer, Integer> t : trees) {
            if (t.getHeight() > depth) depth = t.getHeight();
        }
        depth = (depth + 1) * (MARGIN_WIDTH + 10);

        Layout<Integer,Integer> layoutTree = new TreeLayout<Integer,Integer>(tree);

        Layout<Integer,Integer> layout = new StaticLayout<Integer,Integer>(graph, layoutTree);

        layouts.put(index, layout);
        vm.setGraphLayout(layouts.get(graph_index));

        if (index == schemaGraphIndex) lastSchemaEpisodeNode = depth;
        returnNoSpanningEdges(index, depth);
    }
}
